"""
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    created by lane.chang on '07/09/2022'
    comment: 定义的一些装饰器(service层之上依赖)
"""
import asyncio
import datetime
import functools
import json
import time
import copy
import inspect

from fastapi.responses import JSONResponse
from typing import Optional
from starlette.datastructures import UploadFile
from laner.pydantic import BaseModel

from project.core.log import Colors
from project.core.exception import BizError, NotPathError
from project.core.function import ResponseModel, handler_text, json_response
from project.core.base import BodyParams
from project.lib.constant import PARAMETER_NAMES, DEFAULT_VALUE
from project.lib.common import logger
from project.lib.common import async_session, AsyncScopedSession, AsyncRedis
from project.micro_service.zeus import Zeus


def singleton(cls):
    """类单例模式装饰器
    :param cls:
    :return:
    """
    instance = {}

    @functools.wraps(cls)
    def wrapper(*args, **kwargs):
        if cls not in instance:
            instance[cls] = cls(*args, **kwargs)

        return instance[cls]

    return wrapper


def exec_duration(add_return: bool = False, over_time=800):
    """执行时长装饰器
    :param add_return: 是否添加到返回值
    :param over_time: 超时设定 单位： ms
    :return:
    """

    def handle_func(func):

        @functools.wraps(func)
        async def wrap_func(*args, **kwargs):
            request = kwargs.get('request')
            t1 = datetime.datetime.now().microsecond  # pylint: disable=C0103
            t2 = time.mktime(datetime.datetime.now().timetuple())  # pylint: disable=C0103
            if asyncio.iscoroutinefunction(func):
                ret = await func(*args, **kwargs)
            else:
                ret = func(*args, **kwargs)
            t3 = datetime.datetime.now().microsecond  # pylint: disable=C0103
            t4 = time.mktime(datetime.datetime.now().timetuple())  # pylint: disable=C0103
            exec_time = (t4 - t2) * 1000 + (t3 - t1) / 1000

            log_text = f"函数【{func.__name__}】执行时长: {exec_time}ms"
            logger.info(log_text, '' if not request else request.state.request_id, font_color=Colors.RED.value if exec_time >= over_time else Colors.GREEN.value)

            if add_return:
                if isinstance(ret, dict):
                    ret['exec_time'] = exec_time
                elif isinstance(ret, JSONResponse):
                    data = json.loads(ret.body)
                    data['exec_time'] = exec_time
                    ret = JSONResponse(data)
                elif isinstance(ret, ResponseModel):
                    ret.exec_time = exec_time

            return ret

        return wrap_func

    return handle_func


def api_common(doc_replace=None, verify_login=False, dev_info=None):
    """API统一封装
    :request: API请求体
    :param doc_replace:
    :param verify_login: 是否登陆验证
    :param dev_info: 设备信息
    :return:
    """
    def handle_func(func):
        for replacer in (doc_replace or ()):
            func.__doc__ = func.__doc__.replace(replacer[0], replacer[1])

        @functools.wraps(func)
        async def wrap_func(*args, **kwargs):
            # API包含请求体
            request = kwargs.get('request')
            response = None
            try:
                if request:
                    content_type = request.headers.get("Content-Type", "").lower()
                    if 'application/json' in content_type:
                        # body参数
                        body_params = await request.body()
                        if isinstance(body_params, bytes):
                            body_params = body_params.decode('utf-8').replace('\n', '').replace('   ', '')
                        request.state.body_params = body_params
                        # 写入body体
                        logger.info(f'body: {body_params}', request.state.request_id)
                    elif 'multipart/form-data' in content_type:
                        # form参数
                        request.state.form_params = form_params = await request.form()
                        # 写入body体
                        logger.info(f'form: {form_params}', request.state.request_id)
                # 承接接口参数
                if 'body_params' in kwargs:
                    body_params = BodyParams()
                    for k, v in kwargs.items():
                        if k in PARAMETER_NAMES:
                            continue
                        if v == DEFAULT_VALUE:
                            continue
                        setattr(body_params, k, v)
                    kwargs['body_params'] = body_params

                # 是否登陆验证
                if verify_login:
                    if not request:
                        raise BizError(code=403)
                    token = request.headers.get('authorization')
                    if not token:
                        raise BizError(code=403)
                    if 'Bearer' in token:
                        _, _, token = token.partition(' ')

                    request.state.operator = operator = await Zeus.get_login_info(token)
                    operator.token = token
                    if 'operator' in kwargs:
                        kwargs['operator'] = operator

                if dev_info:
                    if not request:
                        raise BizError(code=403)

                    dev_id = request.headers.get('deviceID')
                    if not dev_id:
                        raise BizError(code=403, message='无设备信息')
                    device = await Zeus.get_device(dev_id)
                    if device.is_forbidden:
                        raise BizError(code=403, message='设备被禁用')
                    if not device.organization_code:
                        raise BizError(code=403, message=f'设备未绑定 {dev_id}')
                    if 'device' in kwargs:
                        kwargs['device'] = device

                if 'session' in kwargs:
                    async with AsyncScopedSession(async_session) as session:
                        kwargs['session'] = session
                        try:
                            response = await func(*args, **kwargs)
                        except Exception as ex:
                            await session.rollback()
                            raise ex
                else:
                    response = await func(*args, **kwargs)

            except NotPathError as ex:
                response = json_response(code=600, message=str(ex))
            except BizError as ex:
                logger.error(ex, '' if not request else request.state.request_id)
                response = json_response(code=ex.code, message=ex.message)
            except Exception as ex:
                logger.critical(ex, '' if not request else request.state.request_id, exc_info=True)
                response = json_response(code=500)
            finally:
                res_body = ''
                if isinstance(response, JSONResponse):
                    res_body = response.body.decode("utf-8").replace('\n', '').replace('   ', '')
                elif isinstance(response, ResponseModel):
                    res_body = str(response.dict()) if response else None

                logger.info(f'{handler_text(res_body, 1000)}', '' if not request else request.state.request_id)

            return response

        return wrap_func

    return handle_func


def convert_datetime(fields: Optional[list[dict[str, tuple]]] = None):
    """ 将对象指定字段
    :param fields:
    :return:
    """

    def handle_func(func):

        @functools.wraps(func)
        async def wrap_func(*args, **kwargs):
            try:
                def _convert_datetime(_model, _keys):
                    """ 将对象中指定时间字段转换成datetime
                    :param _model:
                    :param _keys:
                    :return:
                    """
                    for _key in _keys:
                        if not hasattr(_model, _key):
                            continue
                        value = getattr(_model, _key)
                        if isinstance(value, int):
                            value = datetime.datetime.fromtimestamp(int(str(value)[:10]))
                        elif isinstance(value, str):
                            value = datetime.datetime.strptime(value.replace('/', '-'), '%Y-%m-%d %H:%M:%S')
                        setattr(_model, _key, value)

                # API包含请求体
                for field in fields:
                    for field_path, keys in field.items():

                        model_names = field_path.split('.')
                        root_model = kwargs.pop(model_names[0], None)
                        if not root_model:
                            continue

                        _model = copy.copy(root_model)
                        for model_name in model_names[1:]:
                            _model = getattr(_model, model_name)

                        if isinstance(_model, dict):
                            _convert_datetime(_model, keys)
                        elif isinstance(_model, list):
                            for v in _model:
                                _convert_datetime(v, keys)

                        kwargs[model_names[0]] = root_model

                if asyncio.iscoroutinefunction(func):
                    response = await func(*args, **kwargs)
                else:
                    response = func(*args, **kwargs)

            except Exception as ex:
                print(ex)
                response = json_response(code=600, message=str(ex))

            return response

        return wrap_func

    return handle_func


def func_log(_logger=logger, title=''):
    """
    :param _logger:
    :param title:
    :return:
    """
    def handle_func(func):
        @functools.wraps(func)
        def wrap_func(*args, **kwargs):
            _logger.info(f'title: {title}  function: {func.__name__} 执行日志', font_color=Colors.PURPLE.value)
            _logger.info(f'参数: ', font_color=Colors.PURPLE.value)

            params = dict(zip(inspect.signature(func).parameters.keys(), args))
            params.update(kwargs)
            for k, v in params.items():
                _logger.info(f'     @param {k}: {v}', font_color=Colors.PURPLE.value)

            try:
                t1 = datetime.datetime.now().microsecond  # pylint: disable=C0103
                t2 = time.mktime(datetime.datetime.now().timetuple())  # pylint: disable=C0103
                ret = func(*args, **kwargs)

                if isinstance(ret, BaseModel):
                    ret_log = ret.dict()
                else:
                    ret_log = ret

                t3 = datetime.datetime.now().microsecond  # pylint: disable=C0103
                t4 = time.mktime(datetime.datetime.now().timetuple())  # pylint: disable=C0103
                _logger.info(f'返回: {ret_log if ret_log is not None else ""}', font_color=Colors.PURPLE.value)
                _logger.info(f'执行时长:{(t4 - t2) * 1000 + (t3 - t1) / 1000}', font_color=Colors.PURPLE.value)

                return ret
            except Exception as ex:
                _logger.critical(f'异常: {str(ex)}', exc_info=True, font_color=Colors.PURPLE.value)

        return wrap_func

    return handle_func


def with_async_redis():
    """
    :return:
    """
    def handle_func(func):
        @functools.wraps(func)
        async def wrap_func(*args, **kwargs):
            try:
                async with AsyncRedis() as rds:
                    kwargs['rds'] = rds
                    if asyncio.iscoroutinefunction(func):
                        ret = await func(*args, **kwargs)
                    else:
                        ret = func(*args, **kwargs)

                    return ret

            except Exception as ex:
                raise ex

        return wrap_func

    return handle_func


def with_async_session():
    """
    :return:
    """
    def handle_func(func):
        @functools.wraps(func)
        async def wrap_func(*args, **kwargs):
            try:
                with AsyncScopedSession(async_session) as session:
                    kwargs['session'] = session
                    if asyncio.iscoroutinefunction(func):
                        ret = await func(*args, **kwargs)
                    else:
                        ret = func(*args, **kwargs)

                    return ret

            except Exception as ex:
                await session.rollback()
                raise ex

        return wrap_func

    return handle_func


